/*
 ------------------------------------------------------------------------------
 Copyright (C) 1996-2001 Id Software, Inc.

 This file is part of the Quake source code.

 The Quake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// angles.h - angles math library
//


#ifndef __MATH_ANGLES_H__
#define __MATH_ANGLES_H__


// Angle indices
#define	PITCH						0		// Up / Down
#define	YAW							1		// Left / Right
#define	ROLL						2		// Fall over

extern glqAngles		angles_zero;

class glqAngles {
public:
	float			pitch;
	float			yaw;
	float			roll;

public:
					glqAngles (void);
	explicit		glqAngles (const glqVec3 &vec);
	explicit		glqAngles (const float value);
	explicit		glqAngles (const float pitch, const float yaw, const float roll);

	float			operator [] (const int index) const;
	float &			operator [] (const int index);

	glqAngles &		operator = (const glqAngles &angles);
	bool			operator == (const glqAngles &angles) const;
	bool			operator != (const glqAngles &angles) const;

	glqAngles		operator - () const;

	glqAngles		operator + (const glqAngles &angles) const;
	glqAngles		operator - (const glqAngles &angles) const;
	glqAngles		operator + (const float value) const;
	glqAngles		operator - (const float value) const;
	glqAngles		operator * (const float value) const;
	glqAngles		operator / (const float value) const;

	glqAngles &		operator += (const glqAngles &angles);
	glqAngles &		operator -= (const glqAngles &angles);
	glqAngles &		operator += (const float value);
	glqAngles &		operator -= (const float value);
	glqAngles &		operator *= (const float value);
	glqAngles &		operator /= (const float value);

	bool			Compare (const glqAngles &angles) const;
	bool			Compare (const glqAngles &angles, const float epsilon) const;
	void			Set (const glqVec3 &vec);
	void			Set (const float value);
	void			Set (const float pitch, const float yaw, const float roll);
	void			Clear (void);
	bool			IsCleared (void) const;
	void			Clamp (const glqAngles &min, const glqAngles &max);
	void			Clamp (const float min, const float max);
	void			Lerp (const glqAngles &from, const glqAngles &to, const float frac);
	void			LerpFast (const glqAngles &from, const glqAngles &to, const float frac);
	void			Normalize360 (void);
	void			Normalize180 (void);

	const float *	ToFloatPtr (void) const;
	float *			ToFloatPtr (void);

	void			ToVectors (glqVec3 *forward = NULL, glqVec3 *right = NULL, glqVec3 *up = NULL) const;
	glqVec3			ToForward (void) const;
	glqVec3			ToRight (void) const;
	glqVec3			ToUp (void) const;
	glqQuat			ToQuat (void) const;
	glqMat3			ToMat3 (void) const;
	glqMat4			ToMat4 (void) const;
	const char *	ToString (int precision = 2) const;
};

INLINE glqAngles::glqAngles (void){

}

INLINE glqAngles::glqAngles (const glqVec3 &vec){

	this->pitch = vec.x;
	this->yaw = vec.y;
	this->roll = vec.z;
}

INLINE glqAngles::glqAngles (const float value){

	this->pitch = value;
	this->yaw = value;
	this->roll = value;
}

INLINE glqAngles::glqAngles (const float pitch, const float yaw, const float roll){

	this->pitch = pitch;
	this->yaw = yaw;
	this->roll = roll;
}

INLINE float glqAngles::operator [] (const int index) const {

	return (&pitch)[index];
}

INLINE float &glqAngles::operator [] (const int index){

	return (&pitch)[index];
}

INLINE glqAngles &glqAngles::operator = (const glqAngles &angles){

	pitch = angles.pitch;
	yaw = angles.yaw;
	roll = angles.roll;

	return *this;
}

INLINE bool glqAngles::operator == (const glqAngles &angles) const {

	if (pitch != angles.pitch || yaw != angles.yaw || roll != angles.roll)
		return false;

	return true;
}

INLINE bool glqAngles::operator != (const glqAngles &angles) const {

	if (pitch == angles.pitch && yaw == angles.yaw && roll == angles.roll)
		return false;

	return true;
}

INLINE glqAngles glqAngles::operator - () const {

	return glqAngles(-pitch, -yaw, -roll);
}

INLINE glqAngles glqAngles::operator + (const glqAngles &angles) const {

	return glqAngles(pitch + angles.pitch, yaw + angles.yaw, roll + angles.roll);
}

INLINE glqAngles glqAngles::operator - (const glqAngles &angles) const {

	return glqAngles(pitch - angles.pitch, yaw - angles.yaw, roll - angles.roll);
}

INLINE glqAngles glqAngles::operator + (const float value) const {

	return glqAngles(pitch + value, yaw + value, roll + value);
}

INLINE glqAngles glqAngles::operator - (const float value) const {

	return glqAngles(pitch - value, yaw - value, roll - value);
}

INLINE glqAngles glqAngles::operator * (const float value) const {

	return glqAngles(pitch * value, yaw * value, roll * value);
}

INLINE glqAngles glqAngles::operator / (const float value) const {

	float	scale = 1.0f / value;

	return glqAngles(pitch * scale, yaw * scale, roll * scale);
}

INLINE glqAngles &glqAngles::operator += (const glqAngles &angles){

	pitch += angles.pitch;
	yaw += angles.yaw;
	roll += angles.roll;

	return *this;
}

INLINE glqAngles &glqAngles::operator -= (const glqAngles &angles){

	pitch -= angles.pitch;
	yaw -= angles.yaw;
	roll -= angles.roll;

	return *this;
}

INLINE glqAngles &glqAngles::operator += (const float value){

	pitch += value;
	yaw += value;
	roll += value;

	return *this;
}

INLINE glqAngles &glqAngles::operator -= (const float value){

	pitch -= value;
	yaw -= value;
	roll -= value;

	return *this;
}

INLINE glqAngles &glqAngles::operator *= (const float value){

	pitch *= value;
	yaw *= value;
	roll *= value;

	return *this;
}

INLINE glqAngles &glqAngles::operator /= (const float value){

	float	scale = 1.0f / value;

	pitch *= scale;
	yaw *= scale;
	roll *= scale;

	return *this;
}

INLINE bool glqAngles::Compare (const glqAngles &angles) const {

	if (pitch != angles.pitch || yaw != angles.yaw || roll != angles.roll)
		return false;

	return true;
}

INLINE bool glqAngles::Compare (const glqAngles &angles, const float epsilon) const {

	if (glqMath::FAbs(pitch - angles.pitch) > epsilon)
		return false;
	if (glqMath::FAbs(yaw - angles.yaw) > epsilon)
		return false;
	if (glqMath::FAbs(roll - angles.roll) > epsilon)
		return false;

	return true;
}

INLINE void glqAngles::Set (const glqVec3 &vec){

	this->pitch = vec.x;
	this->yaw = vec.y;
	this->roll = vec.z;
}

INLINE void glqAngles::Set (const float value){

	this->pitch = value;
	this->yaw = value;
	this->roll = value;
}

INLINE void glqAngles::Set (const float pitch, const float yaw, const float roll){

	this->pitch = pitch;
	this->yaw = yaw;
	this->roll = roll;
}

INLINE void glqAngles::Clear (void){

	pitch = 0.0f;
	yaw = 0.0f;
	roll = 0.0f;
}

INLINE bool glqAngles::IsCleared (void) const {

	if (pitch != 0.0f || yaw != 0.0f || roll != 0.0f)
		return false;

	return true;
}

INLINE void glqAngles::Clamp (const glqAngles &min, const glqAngles &max){

	if (pitch < min.pitch)
		pitch = min.pitch;
	else if (pitch > max.pitch)
		pitch = max.pitch;

	if (yaw < min.yaw)
		yaw = min.yaw;
	else if (yaw > max.yaw)
		yaw = max.yaw;

	if (roll < min.roll)
		roll = min.roll;
	else if (roll > max.roll)
		roll = max.roll;
}

INLINE void glqAngles::Clamp (const float min, const float max){

	if (pitch < min)
		pitch = min;
	else if (pitch > max)
		pitch = max;

	if (yaw < min)
		yaw = min;
	else if (yaw > max)
		yaw = max;

	if (roll < min)
		roll = min;
	else if (roll > max)
		roll = max;
}

INLINE void glqAngles::Lerp (const glqAngles &from, const glqAngles &to, const float frac){

	if (frac <= 0.0f){
		*this = from;
		return;
	}

	if (frac >= 1.0f){
		*this = to;
		return;
	}

	if (to.pitch - from.pitch > 180.0f)
		pitch = from.pitch + ((to.pitch - 360.0f) - from.pitch) * frac;
	else if (to.pitch - from.pitch < -180.0f)
		pitch = from.pitch + ((to.pitch + 360.0f) - from.pitch) * frac;
	else
		pitch = from.pitch + (to.pitch - from.pitch) * frac;

	if (to.yaw - from.yaw > 180.0f)
		yaw = from.yaw + ((to.yaw - 360.0f) - from.yaw) * frac;
	else if (to.yaw - from.yaw < -180.0f)
		yaw = from.yaw + ((to.yaw + 360.0f) - from.yaw) * frac;
	else
		yaw = from.yaw + (to.yaw - from.yaw) * frac;

	if (to.roll - from.roll > 180.0f)
		roll = from.roll + ((to.roll - 360.0f) - from.roll) * frac;
	else if (to.roll - from.roll < -180.0f)
		roll = from.roll + ((to.roll + 360.0f) - from.roll) * frac;
	else
		roll = from.roll + (to.roll - from.roll) * frac;
}

INLINE void glqAngles::LerpFast (const glqAngles &from, const glqAngles &to, const float frac){

	if (to.pitch - from.pitch > 180.0f)
		pitch = from.pitch + ((to.pitch - 360.0f) - from.pitch) * frac;
	else if (to.pitch - from.pitch < -180.0f)
		pitch = from.pitch + ((to.pitch + 360.0f) - from.pitch) * frac;
	else
		pitch = from.pitch + (to.pitch - from.pitch) * frac;

	if (to.yaw - from.yaw > 180.0f)
		yaw = from.yaw + ((to.yaw - 360.0f) - from.yaw) * frac;
	else if (to.yaw - from.yaw < -180.0f)
		yaw = from.yaw + ((to.yaw + 360.0f) - from.yaw) * frac;
	else
		yaw = from.yaw + (to.yaw - from.yaw) * frac;

	if (to.roll - from.roll > 180.0f)
		roll = from.roll + ((to.roll - 360.0f) - from.roll) * frac;
	else if (to.roll - from.roll < -180.0f)
		roll = from.roll + ((to.roll + 360.0f) - from.roll) * frac;
	else
		roll = from.roll + (to.roll - from.roll) * frac;
}

INLINE void glqAngles::Normalize360 (void){

	if (pitch < 0.0f || pitch >= 360.0f)
		pitch -= 360.0f * glqMath::Floor(pitch * (1.0f / 360.0f));

	if (yaw < 0.0f || yaw >= 360.0f)
		yaw -= 360.0f * glqMath::Floor(yaw * (1.0f / 360.0f));

	if (roll < 0.0f || roll >= 360.0f)
		roll -= 360.0f * glqMath::Floor(roll * (1.0f / 360.0f));
}

INLINE void glqAngles::Normalize180 (void){

	if (pitch < 0.0f || pitch >= 360.0f)
		pitch -= 360.0f * glqMath::Floor(pitch * (1.0f / 360.0f));
	if (pitch > 180.0f)
		pitch -= 360.0f;

	if (yaw < 0.0f || yaw >= 360.0f)
		yaw -= 360.0f * glqMath::Floor(yaw * (1.0f / 360.0f));
	if (yaw > 180.0f)
		yaw -= 360.0f;

	if (roll < 0.0f || roll >= 360.0f)
		roll -= 360.0f * glqMath::Floor(roll * (1.0f / 360.0f));
	if (roll> 180.0f)
		roll -= 360.0f;
}

INLINE const float *glqAngles::ToFloatPtr (void) const {

	return &pitch;
}

INLINE float *glqAngles::ToFloatPtr (void){

	return &pitch;
}


#endif	// __MATH_ANGLES_H__